/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
*    http://www.geo-solutions.it/
*    Copyright 2014 GeoSolutions


* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.imagen.media.bandmerge;

import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderableImage;
import java.util.List;
import org.eclipse.imagen.JAI;
import org.eclipse.imagen.OperationDescriptorImpl;
import org.eclipse.imagen.ParameterBlockJAI;
import org.eclipse.imagen.ROI;
import org.eclipse.imagen.RenderableOp;
import org.eclipse.imagen.RenderedOp;
import org.eclipse.imagen.media.range.Range;
import org.eclipse.imagen.registry.RenderableRegistryMode;
import org.eclipse.imagen.registry.RenderedRegistryMode;

/**
 * An <code>OperationDescriptor</code> describing the "BandMerge" operation.
 *
 * <p>The BandMerge operation takes two or more rendered or renderable images, and performs the merging of all the image
 * bands inside a singular multibanded image. Each source image should be non-null. The number of bands of the
 * destination image is the sum of those of all source images.
 *
 * <p>All the bands of source images are ordered in the order given in a ParameterBlock. If image1 has three bands and
 * image2 has one band, the merged image has 4 bands. Three grayscale images can be used to merge into an RGB image in
 * this way.
 *
 * <p>The destination image bound is the intersection of the source image bounds. If the sources don't intersect, the
 * destination will have a width and height of 0.
 *
 * <p>If No Data values are present, then the user can define an array of {@link Range} objects(each one for an image)
 * for the No Data and a double value called "destination" no data which will be set instead of the old No data value.
 * If the array length is smaller than that of the input images, then the first Range object is taken.
 *
 * <p>If the user wants to do a backward mapping from the destination pixels to each source image pixel, then he must
 * add a parameter called "transformations". This parameter is a List of the transformations to perform on each image.
 * Note that the list size must be equal to that of the sources, because each transformation must be related to only one
 * image. The user should remember that these transformations have to map from the destination image to the source image
 * and not the opposite.
 *
 * <p>
 *
 * <table border=1>
 * <caption>Resource List</caption>
 * <tr>
 * <th>Name</th>
 * <th>Value</th>
 * </tr>
 * <tr>
 * <td>GlobalName</td>
 * <td>BandMerge</td>
 * </tr>
 * <tr>
 * <td>LocalName</td>
 * <td>BandMerge</td>
 * </tr>
 * <tr>
 * <td>Vendor</td>
 * <td>org.eclipse.imagen.media</td>
 * </tr>
 * <tr>
 * <td>Description</td>
 * <td>Operation used for merging multiple images into a single multibanded image.</td>
 * </tr>
 * <tr>
 * <td>DocURL</td>
 * <td>Not Defined</td>
 * </tr>
 * <tr>
 * <td>Version</td>
 * <td>1.0</td>
 * </tr>
 * <td>arg0Desc</td>
 * <td>NoData values.</td>
 * </tr>
 * <td>arg1Desc</td>
 * <td>Destination No Data value.</td>
 * </tr>
 * <td>arg2Desc</td>
 * <td>Transformations List.</td>
 * </tr>
 * <td>arg3Desc</td>
 * <td>ROI object to use.</td>
 * </tr>
 * </table>
 *
 * <p>
 *
 * <table border=1>
 * <caption>Parameter List</caption>
 * <tr>
 * <th>Name</th>
 * <th>Class Type</th>
 * <th>Default Value</th>
 * </tr>
 * <tr>
 * <td>noData</td>
 * <td>Range[]</td>
 * <td>null</td>
 * <tr>
 * <td>destinationNoData</td>
 * <td>Double</td>
 * <td>0</td>
 * <tr>
 * <td>transformations</td>
 * <td>java.util.List</td>
 * <td>null</td>
 * <tr>
 * <td>roi</td>
 * <td>org.eclipse.imagen.ROI</td>
 * <td>null</td>
 * <tr>
 * <tr>
 * </table>
 *
 * <p>
 *
 * @see org.eclipse.imagen.OperationDescriptor
 */
public class BandMergeDescriptor extends OperationDescriptorImpl {

    /**
     * The resource strings that provide the general documentation and specify the parameter list for this operation.
     */
    private static final String[][] resources = {
        {"GlobalName", "BandMerge"},
        {"LocalName", "BandMerge"},
        {"Vendor", "org.eclipse.imagen.media"},
        {"Description", "Operation used for merging multiple images into a single multibanded image"},
        {"DocURL", "Not Defined"},
        {"Version", "1.0"},
        {"arg0Desc", "NoData values"},
        {"arg1Desc", "Destination No Data value"},
        {"arg2Desc", "Transformations List"},
        {"arg3Desc", "ROI object to use"},
        {"arg4Desc", "Boolean indicating if the last band is an alpha band"}
    };

    /** Input Parameter name */
    private static final String[] paramNames = {"noData", "destinationNoData", "transformations", "roi", "setAlpha"};

    /** Input Parameter class */
    private static final Class[] paramClasses = {
        org.eclipse.imagen.media.range.Range[].class,
        Double.class,
        List.class,
        org.eclipse.imagen.ROI.class,
        Boolean.class
    };

    /** Input Parameter default values */
    private static final Object[] paramDefaults = {null, 0d, null, null, false};

    /** Constructor. */
    public BandMergeDescriptor() {
        super(resources, paramClasses, paramNames, paramDefaults);
    }

    /** Returns <code>true</code> since renderable operation is supported. */
    public boolean isRenderableSupported() {
        return true;
    }

    /**
     * Merge (possibly multi-banded)images into a multibanded image.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#create(String,ParameterBlock,RenderingHints)}.
     *
     * @see JAI
     * @see ParameterBlockJAI
     * @see RenderedOp
     * @param noData Array of input No Data Ranges.
     * @param destinationNoData value used by the RenderedOp for setting the output no data value.
     * @param setAlpha boolean used for setting the last band as alpha band
     * @param hints The <code>RenderingHints</code> to use. May be <code>null</code>.
     * @param sources Array of source <code>RenderedImage</code>.
     * @return The <code>RenderedOp</code> destination.
     * @throws IllegalArgumentException if <code>sources</code> is <code>null</code>.
     * @throws IllegalArgumentException if a <code>source</code> is <code>null</code>.
     */
    public static RenderedOp create(
            Range[] noData,
            double destinationNoData,
            boolean setAlpha,
            RenderingHints hints,
            RenderedImage... sources) {
        return create(noData, destinationNoData, setAlpha, hints, null, sources);
    }

    /**
     * Merge (possibly multi-banded)images into a multibanded image.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#create(String,ParameterBlock,RenderingHints)}.
     *
     * @see JAI
     * @see ParameterBlockJAI
     * @see RenderedOp
     * @param noData Array of input No Data Ranges.
     * @param destinationNoData value used by the RenderedOp for setting the output no data value.
     * @param setAlpha boolean used for setting the last band as alpha band
     * @param hints The <code>RenderingHints</code> to use. May be <code>null</code>.
     * @param transform A List of AffineTransformation to use for backward mapping each source image. May be <code>null
     *     </code>.
     * @param sources Array of source <code>RenderedImage</code>.
     * @return The <code>RenderedOp</code> destination.
     * @throws IllegalArgumentException if <code>sources</code> is <code>null</code>.
     * @throws IllegalArgumentException if a <code>source</code> is <code>null</code>.
     */
    public static RenderedOp create(
            Range[] noData,
            double destinationNoData,
            boolean setAlpha,
            RenderingHints hints,
            List<AffineTransform> transform,
            RenderedImage... sources) {
        return create(noData, destinationNoData, setAlpha, hints, transform, null, sources);
    }

    /**
     * Merge (possibly multi-banded)images into a multibanded image.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#create(String,ParameterBlock,RenderingHints)}.
     *
     * @see JAI
     * @see ParameterBlockJAI
     * @see RenderedOp
     * @param noData Array of input No Data Ranges.
     * @param destinationNoData value used by the RenderedOp for setting the output no data value.
     * @param hints The <code>RenderingHints</code> to use. May be <code>null</code>.
     * @param transform A List of AffineTransformation to use for backward mapping each source image. May be <code>null
     *     </code>.
     * @param roi Input ROI object to use in the bandmerge operation.
     * @param sources Array of source <code>RenderedImage</code>.
     * @return The <code>RenderedOp</code> destination.
     * @throws IllegalArgumentException if <code>sources</code> is <code>null</code>.
     * @throws IllegalArgumentException if a <code>source</code> is <code>null</code>.
     */
    public static RenderedOp create(
            Range[] noData,
            double destinationNoData,
            boolean setAlpha,
            RenderingHints hints,
            List<AffineTransform> transform,
            ROI roi,
            RenderedImage... sources) {
        ParameterBlockJAI pb = new ParameterBlockJAI("BandMerge", RenderedRegistryMode.MODE_NAME);
        // Source number
        int numSources = sources.length;
        // Check on the source number
        if (numSources <= 0) {
            throw new IllegalArgumentException("No resources are present");
        }

        // Setting of all the sources
        for (int index = 0; index < numSources; index++) {

            RenderedImage source = sources[index];

            if (source == null) {
                throw new IllegalArgumentException("This resource is null");
            }

            pb.setSource(source, index);
        }

        // Check if the transform object can be used
        if (transform != null && !transform.isEmpty() && transform.get(0) instanceof AffineTransform) {
            pb.setParameter("transformations", transform);
        }

        // Setting of the parameters
        pb.setParameter("noData", noData);
        pb.setParameter("destinationNoData", destinationNoData);
        pb.setParameter("roi", roi);
        pb.setParameter("setAlpha", setAlpha);

        // Creation of the RenderedOp
        return JAI.create("BandMerge", pb, hints);
    }

    /**
     * Merge (possibly multi-banded)images into a multibanded image.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#createRenderable(String,ParameterBlock,RenderingHints)}.
     *
     * @see JAI
     * @see ParameterBlockJAI
     * @see RenderedOp
     * @param noData Array of input No Data Ranges.
     * @param destinationNoData value used by the RenderableOp for setting the output no data value.
     * @param hints The <code>RenderingHints</code> to use. May be <code>null</code>.
     * @param sources Array of source <code>RenderableImage</code>.
     * @return The <code>RenderableOp</code> destination.
     * @throws IllegalArgumentException if <code>sources</code> is <code>null</code>.
     * @throws IllegalArgumentException if a <code>source</code> is <code>null</code>.
     */
    public static RenderableOp createRenderable(
            Range[] noData, double destinationNoData, RenderingHints hints, RenderableImage... sources) {
        return createRenderable(noData, destinationNoData, hints, null, sources);
    }

    /**
     * Merge (possibly multi-banded)images into a multibanded image.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#createRenderable(String,ParameterBlock,RenderingHints)}.
     *
     * @see JAI
     * @see ParameterBlockJAI
     * @see RenderedOp
     * @param noData Array of input No Data Ranges.
     * @param destinationNoData value used by the RenderableOp for setting the output no data value.
     * @param hints The <code>RenderingHints</code> to use. May be <code>null</code>.
     * @param transform A List of AffineTransformation to use for backward mapping each source image. May be <code>null
     *     </code>.
     * @param sources Array of source <code>RenderableImage</code>.
     * @return The <code>RenderableOp</code> destination.
     * @throws IllegalArgumentException if <code>sources</code> is <code>null</code>.
     * @throws IllegalArgumentException if a <code>source</code> is <code>null</code>.
     */
    public static RenderableOp createRenderable(
            Range[] noData,
            double destinationNoData,
            RenderingHints hints,
            List<AffineTransform> transform,
            RenderableImage... sources) {
        return createRenderable(noData, destinationNoData, hints, transform, null, sources);
    }

    /**
     * Merge (possibly multi-banded)images into a multibanded image.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#createRenderable(String,ParameterBlock,RenderingHints)}.
     *
     * @see JAI
     * @see ParameterBlockJAI
     * @see RenderedOp
     * @param noData Array of input No Data Ranges.
     * @param destinationNoData value used by the RenderableOp for setting the output no data value.
     * @param hints The <code>RenderingHints</code> to use. May be <code>null</code>.
     * @param transform A List of AffineTransformation to use for backward mapping each source image. May be <code>null
     *     </code>.
     * @param roi Input ROI object to use in the bandmerge operation.
     * @param sources Array of source <code>RenderableImage</code>.
     * @return The <code>RenderableOp</code> destination.
     * @throws IllegalArgumentException if <code>sources</code> is <code>null</code>.
     * @throws IllegalArgumentException if a <code>source</code> is <code>null</code>.
     */
    public static RenderableOp createRenderable(
            Range[] noData,
            double destinationNoData,
            RenderingHints hints,
            List<AffineTransform> transform,
            ROI roi,
            RenderableImage... sources) {
        ParameterBlockJAI pb = new ParameterBlockJAI("BandMerge", RenderableRegistryMode.MODE_NAME);
        // Source number
        int numSources = sources.length;
        // Check on the source number
        if (numSources <= 0) {
            throw new IllegalArgumentException("No resources are present");
        }
        // Setting of all the sources
        for (int index = 0; index < numSources; index++) {

            RenderableImage source = sources[index];

            if (source == null) {
                throw new IllegalArgumentException("This resource is null");
            }

            pb.setSource(source, index);
        }

        // Check if the transform object can be used
        if (transform != null && !transform.isEmpty() && transform.get(0) instanceof AffineTransform) {
            pb.setParameter("transformations", transform);
        }

        // Setting of the parameters
        pb.setParameter("noData", noData);
        pb.setParameter("destinationNoData", destinationNoData);
        pb.setParameter("roi", roi);

        // Creation of the RenderedOp
        return JAI.createRenderable("BandMerge", pb, hints);
    }
}
